#include "vlog.h"
#include <libgen.h>

#if _VLOG_SUPPORT_THREAD_

#define vlock_guard(o_mutex)  \
    std::lock_guard<std::mutex> o_lock(o_mutex); 
#define vlog_lock(lck) lck.lock();           
#define vlog_unlock(lck) lck.unlock();        

#else  //#if _VLOG_SUPPORT_THREAD_

#define vlock_guard(o_mutex)  
#define vlog_lock(lck)
#define vlog_unlock(lck)

#endif //end of #if _VLOG_SUPPORT_THREAD_

////////////////////////////////////////////////////////////////////
//vlog static member variable define
thread_local vlog_thread vlog::_vlog_thread;


vlog::vlog() noexcept
{
#ifdef _VLOG_LEVEL_
    if(nvlog::trace <= _VLOG_LEVEL_ && _VLOG_LEVEL_ <= nvlog::critical)
    {
        _user_want_level = static_cast<nvlog::level>(_VLOG_LEVEL_);
    }
    else
    {
        std::cout<<"vlog define log levle is invalid, the invalid value is:"
            <<_VLOG_LEVEL_<<", not in ["<<nvlog::trace<<","
            <<nvlog::critical<<"]"
            <<std::endl;
    }
#else
    std::cout<<"vlog use default log levle:debug"<<std::endl;
#endif

    char * plog = getenv("_VLOG_LEVEL_");
    if(plog!=NULL)
    {
        auto lenv = std::stoi(std::string(plog));
        if(nvlog::trace <= lenv  && lenv <= nvlog::critical)
        {
            _user_want_level = static_cast<nvlog::level>(lenv);
            //std::cout<<"vlog set log level by environment value."<<std::endl;
        }
        else
        {
            std::cout<<"vlog env log levle is invalid, the invalid value is:"
                <<lenv<<", not in ["<<nvlog::debug<<","<<nvlog::critical<<"]"
                <<std::endl;
        }
    }
    //std::cout<<"vlog set vlog levle to be "<<_user_want_level<<std::endl;
}

vlog::~vlog()
{

}

void vlog::set_level(nvlog::level l)
{
    _user_want_level = l;
}

bool vlog::should_log() const
{
    return _level>=_user_want_level;
}

//bool vlog::should_log(nvlog::level user_level) const
//{
//    return user_level>=_user_want_level;
//}

std::mutex & vlog::get_mutex() const
{
    return _mutex_vlog;
}

vlog & vlog::log_out(nvlog::level level_cur,const std::string &file, 
        int line, 
        const std::string & function,
        std::unique_lock<std::mutex> & unq_lock)
{
    auto & vth = get_vlog_thread();
    auto & vstr = vth.push();

    vstr.set_level(level_cur);
    vstr.set_seting_level(_user_want_level);

    if(vstr.should_log())
    {
        vstr.set_header(file,line,function);
    }
    return *this;
}

void vlog::operator<< (std_endl_t pf)
{
    const auto & vth = get_vlog_thread();
    const auto & vstr = vth.top();
    if(vstr.should_log())
    {
        //vstr.log_out();
        //two way,same result,but first would be more performance.

        pf(std::cout);
    }

    if(_pf_endl == pf)
    {
    }
    else
    {
        std::cout<<"current.pf.is.not endl"<<std::endl;
    }
    //return *this;
}

std::string vlog::str() const
{
    const auto & vth = get_vlog_thread();
    return vth.str();
}

std::string vlog::information()
{
    bool is_multiple_th = true;
#if _VLOG_SUPPORT_THREAD_
    is_multiple_th=true;

#else  //#if _VLOG_SUPPORT_THREAD_
    is_multiple_th=false;
#endif //end of #if _VLOG_SUPPORT_THREAD_

    vlogi<<"user setting log_level:"
        <<vlog_str::get_level_str(_user_want_level)<<","
        <<"multiple thread mode:"<<is_multiple_th
        <<std::endl;

    return std::string();
}

bool vlog::is_current_thread_locked() const
{
    auto tid = std::this_thread::get_id();
    return tid==_current_locked_tid;
}

void vlog::set_current_thread_locked() 
{
    _current_locked_tid = std::this_thread::get_id();
}

void vlog::set_current_thread_unlocked()
{
    _current_locked_tid = std::thread::id();
}

const vlog_thread & vlog::get_vlog_thread() const
{
    return _vlog_thread;
}

vlog_thread & vlog::get_vlog_thread()
{
    return _vlog_thread;
}


//////////////////////////////vlog_thread////////////////////////////////////

vlog_thread::vlog_thread()
{
}

vlog_thread::~vlog_thread()
{

}

std::string vlog_thread::str() const
{
    return _str;
}

bool vlog_thread::empty() const
{
    return _stack_vlog_str.empty();
}

size_t vlog_thread::size() const
{
    return _stack_vlog_str.size();
}

void vlog_thread::pop()
{
    _str = std::move(_stack_vlog_str.top().str());
    _stack_vlog_str.pop();
}

const vlog_str & vlog_thread::top() const
{
    return _stack_vlog_str.top();
}

vlog_str & vlog_thread::top()
{
    return _stack_vlog_str.top();
}

vlog_str & vlog_thread::push()
{
    //_str.clear();
    _stack_vlog_str.emplace();
    return _stack_vlog_str.top();
}

/////////////////////////////////vlog_str//////////////////////////////////

vlog_str::vlog_str()
{

}

vlog_str::~vlog_str()
{

}

const std::string & vlog_str::str() const
{
    return _content;
}

std::string & vlog_str::str()
{
    return _content;
}

bool vlog_str::should_log() const
{
    return _level>=_user_settng_level;
}

void vlog_str::set_level(nvlog::level l) //current log out level
{
    _level = l;
}

void vlog_str::set_seting_level(nvlog::level l) //user seting level
{
    _user_settng_level = l;
}

void vlog_str::log_out() const
{
    std::cout<<_header<<_content; 
}

void vlog_str::set_header(const std::string & file, int line, const std::string & function)
{
    if(!file.empty())
    {
        _file = std::string(basename(const_cast<char*>(file.c_str())));
    }
    _line = line;
    _function=function+"()";

    _header = "["+vbase::vtime::now().substr(5)+"] ["
        +get_level_str(_level)+"] ["
        +_file+":"+std::to_string(_line)+" "
        +_function+"] ";
    std::cout<<_header; 
}

std::string vlog_str::get_level_str(nvlog::level _level_)
{
    std::string ret;
    switch (_level_)
    { 
        case nvlog::trace:
            ret = "trace";
            break;
        case nvlog::debug:
            ret = "debug";
            break;
        case nvlog::detail:
            ret = "detail";
            break;
        case nvlog::info:
            ret = "info";
            break;
        case nvlog::warn:
            ret = "warn";
            break;
        case nvlog::error:
            ret = "error";
            break;
        case nvlog::critical:
            ret = "critical";
            break;
        case nvlog::audit:
            ret = "audit";
            break;

        default:
            ret = "not define the level:";
            ret+= std::to_string(static_cast<int>(_level_));
            break;
    }
    return ret;
}


////////////////////////////////////////vlog_tmp/////////////////////////////////////////
vlog_tmp::vlog_tmp(nvlog::level level_cur,
        const std::string & file, 
        int line, 
        const std::string & function)
    :_file(file),_line(line),_function(function),_level(level_cur),
    _lock(vlog::instance()->get_mutex(),std::defer_lock)
{
    auto sp_vlog = vlog::instance();
    if(sp_vlog->is_current_thread_locked())
    {
        //for embed vlog, up vlog in current thread already locked.
    }
    else
    {
        vlog_lock(_lock);
        sp_vlog->set_current_thread_locked();
    }
}

//would be executed after std::endl that vlog_tmp's tmp object is released
//use RAII to manager lock and some other resource
vlog_tmp::~vlog_tmp()
{
    auto sp_vlog = vlog::instance();
    //exception be throwed out, or vlog end that ended by std::endl;
    auto & vth = sp_vlog->get_vlog_thread();
    vth.pop();

    sp_vlog->set_current_thread_unlocked();
}

vlog & vlog_tmp::get_vlog()
{
    auto sp_vlog = vlog::instance();

    sp_vlog->log_out(_level,_file,_line,_function,_lock);
    return *sp_vlog;
}


